package com.zhoukx.nio.grantChat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 实例要求:
 * 编写一个 NIO 群聊系统，实现服务器端和客户端之间的数据简单通讯（非阻塞）
 * 实现多人群聊
 * 服务器端：可以监测用户上线，离线，并实现消息转发功能
 * 客户端：通过channel 可以无阻塞发送消息给其它所有用户，同时可以接受其它用户发送的消息(有服务器转发得到)
 * 目的：进一步理解NIO非阻塞网络编程机制
 */
public class GrantChatServer {

    // 定义服务器属性
    private Selector selector;

    private ServerSocketChannel listenChannel;

    private static  final int  PORT= 6667;


    /**
     *  初始化操作
     */
    public GrantChatServer(){
        //
        try {
            // 初始化选择器
            selector = Selector.open();

            // 初始化ServerSocketChannel
            listenChannel = ServerSocketChannel.open();

            // 绑定端口
            listenChannel.socket().bind(new InetSocketAddress(PORT));

            //设置为非阻塞IO
            listenChannel.configureBlocking(false);

            // 将listenChannel 注册到selector选择器  第一次连接
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);


        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     *  监听方法
     */
    public void listen(){

        // 循环监听
        while (true) {

            try {

                // 查看有没有事件发生,等于0 没有 大于0 有
                int count = selector.select();

                // 有事件发生需要处理
                if (count > 0) {

                    // 拿到所有的管道
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();

                    Iterator<SelectionKey> iterator = selectionKeys.iterator();

                    // 循环处理每个SelectionKey
                    while (iterator.hasNext()) {

                        // 取出一个selectionKey
                        SelectionKey next = iterator.next();

                        // 监听是否是新注册
                        if (next.isAcceptable()) {

                            // 获取连接
                            SocketChannel channel = listenChannel.accept();

                            // 设置非阻塞IO
                            channel.configureBlocking(false);

                            //将channel注册到selector选择器中
                            channel.register(selector,SelectionKey.OP_READ);

                            //提示那个client上线
                            System.out.println(channel.getRemoteAddress()+ " 上线..");
                        }

                        // 判断是否是read事件
                        if (next.isReadable()) {

                            // 处理读取方法
                            readData(next);
                        }

                        // 删除当前的管道,防止重复
                        iterator.remove();
                    }

                }else{
                    System.out.println("等待连接..");
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  读取数据
     * @param key
     */
    private void readData(SelectionKey key) {

        // 获取关联到的channel  读取用socketChannel
        SocketChannel socketChannel = null;

        try {

            // 获取channel
            socketChannel = (SocketChannel) key.channel();

            // 创建一个缓存
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            //从管道中读取数据
            int read = socketChannel.read(buffer);

            // 根据read值做处理,大于0 代表有数据
            if (read > 0) {

                String msg = new String(buffer.array());

                //输出信息
                System.out.println(" form 客户端: " + msg);

                // 向其他客户端转发自己的信息
                sendInfoToOtherCliets(msg,socketChannel);
            }
        } catch (IOException e) {
            try {

                System.out.println(socketChannel.getRemoteAddress()+" 离线了...");

                // 取消注册
                key.cancel();

                // 关闭通道
                socketChannel.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }

    }

    // 转发客户端信息
    private void sendInfoToOtherCliets(String msg, SocketChannel self) throws IOException {
        System.out.println("服务器转发消息.....");

        //遍历 所有注册到selector上的socketChannel,并排除self
        Set<SelectionKey> keys = selector.keys();

        for (SelectionKey key : keys) {

            // 通过key取出socketChannel
            Channel targetChannel =  key.channel();

            // 排除自己
            if (targetChannel instanceof SocketChannel && targetChannel != self) {

                // 转型
                SocketChannel socketChannel = (SocketChannel) targetChannel;

                // 将msg转到buffer
                ByteBuffer wrap = ByteBuffer.wrap(msg.getBytes());

                // 将数据写到管道中
                socketChannel.write(wrap);
            }


        }
    }


    public static void main(String[] args) {

        GrantChatServer grantChatServer = new GrantChatServer();
        grantChatServer.listen();

    }
}
